﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SmartRoute.Data;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;

namespace SmartRoute.Data
{
    [Serializable]
    /// <summary>
    /// A stop is a location that has a mutable number of passengers.
    /// Two stops at the same location are equivalent even if they have different numbers of passengers.
    /// The number of passengers at a stop defaults to zero.
    /// </summary>
    public class Stop : Location, ICloneable, ISerializable
    {
        protected Stop() { }
        private int _passengers = 0;

        /// <summary>
        /// The number of passengers waiting at this stop.
        /// </summary>
        public int Passengers
        {
            get { return _passengers; }
            set { _passengers = value; }
        }

        /// <summary>
        /// Generates a Stop from an existing location, given a number of passengers.
        /// </summary>
        /// <param name="place">The location where this stop will be placed.</param>
        /// <param name="passengersAtDestination">The passengers waiting at this stop.</param>
        public Stop(Location place, int passengersAtDestination)
        {
            base._address = place.Address;
            base._latitude = place.Latitude;
            base._longitude = place.Longitude;
            Passengers = passengersAtDestination;
        }

        public object Clone()
        {
            object returning = this.MemberwiseClone();
            return returning;
        }

        //method found at http://stackoverflow.com/questions/129389/how-do-you-do-a-deep-copy-an-object-in-net-c-specifically
        public static T DeepCopy<T>(T obj)
        {
            object result = null;

            using (var ms = new MemoryStream())
            {
                var formatter = new BinaryFormatter();
                formatter.Serialize(ms, obj);
                ms.Position = 0;

                result = (T)formatter.Deserialize(ms);
                ms.Close();
            }

            return (T)result;
        }



        #region ISerializable Members

        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("Latitude", this.Latitude);
            info.AddValue("Longitude", this.Longitude);
            //info.AddValue("Passengers", this.Passengers);
            info.AddValue("Address", this.Address);
        }

        private Stop(SerializationInfo info, StreamingContext context)
        {
            this._address = info.GetString("Address");
            this._latitude = info.GetDouble("Latitude");
            this._longitude = info.GetDouble("Longitude");
            //this._passengers = info.GetInt32("Passengers");
        }

        #endregion
    }

    /// <summary>
    /// A bus. It knows its route, and how many students it picks up along it.
    /// It also knows how long a single student spends on a bus.
    /// As a bus is purely data and does not perform any control logic, it does not extend Route.
    /// </summary>
    public struct Bus
    {

        #region Private Fields

        private readonly Location _start;
        private readonly Location _end;
        private readonly bool _trivialFlag;
        private readonly int _passengers;
        private readonly TimeSpan _duration;
        private readonly double _distance;
        //private readonly TimeSpan _startingTime;
        //private readonly TimeSpan _endingTime;

        #endregion

        #region Accessors

        /// <summary>
        /// Where this bus is located before it has serviced any of its passengers.
        /// </summary>
        public Location StartingLocation
        {
            get { return _start; }
        }

        /// <summary>
        /// Where this bus is located after it has serviced all of its passengers.
        /// </summary>
        public Location EndingLocation
        {
            get{ return _end; }
        } 

        /// <summary>
        /// The routes connecting the stops this bus visits together.
        /// </summary>
        public IEnumerable<Route> Directions
        {
            get { return
                   from BusRouteLeg leg in _route
                   select leg.Route;
                }
        }

        /// <summary>
        /// True if this bus does not visit any stops or service any students.
        /// </summary>
        public bool Used
        {
            get { return _trivialFlag; }
        } 

        /// <summary>
        /// The number of passengers picked up by this bus.
        /// </summary>
        public int Passengers
        {
            get { return _passengers; }
        }

        /// <summary>
        /// Allows the routing service used by inner routes to be changed and observed.
        /// </summary>
        public IRoutingService RoutingService
        {
            get { return _route.First().Route.RoutingService; }
            set { foreach (BusRouteLeg leg in _route) leg.Route.RoutingService = value; }
        }

        private readonly TimeSpan _timeStudentSpentOnBus;

        /// <summary>
        /// The longest time a student spends on this bus.
        /// </summary>
        public TimeSpan TimeStudentSpentOnBus
        {
            get { return _timeStudentSpentOnBus; }
        }

        /// <summary>
        /// The total time this bus travels.
        /// </summary>
        public TimeSpan Duration
        {
            get { return _duration; }
        }

        /// <summary>
        /// The total distance travelled by this bus.
        /// </summary>
        public double Distance
        {
            get { return _distance; }
        }

        /// <summary>
        /// The number of stops this bus will visit. Use it to calculate loading times.
        /// By their very nature, loading times count toward time students spend on a bus.
        /// </summary>
        public int StopsMade
        {
            get { return _trivialFlag ? 0 : _route.Length; }
        }

        ///// <summary>
        ///// When this bus begins its route.
        ///// </summary>
        //public TimeSpan StartingTime
        //{
        //    get { return _startingTime; }
        //}

        ///// <summary>
        ///// When this bus ends its route.
        ///// </summary>
        //public TimeSpan EndingTime
        //{
        //    get { return _endingTime; }
        //} 

        #endregion

        #region Construction

        /// <summary>
        /// Represents intermediate stops in a bus routes. 
        /// This struct might be refactored out of the code.
        /// </summary>
        private struct BusRouteLeg
        {
            public readonly Route Route;
            public readonly int Passengers;
            public BusRouteLeg(Route path, int passengersAtDestination)
            {
                Route = path;
                Passengers = passengersAtDestination;
            }
            public BusRouteLeg(Route source, Stop destination)
            {
                Route = source + destination;
                Passengers = destination.Passengers;
            }
            public BusRouteLeg(BusRouteLeg source, Stop destination)
            {
                Route = source.Route.RoutingService.FromTo(
                    source.Route.Destination, destination);
                Passengers = destination.Passengers;
            }
        }
        
        /// <summary>
        /// The actual route used by the bus, which contains information on the stops.
        /// </summary>
        private BusRouteLeg[] _route;

        /// <summary>
        /// Constructs a new bus, given a starting location and a list of stops.
        /// The last stop provided is assumed to be the bus' destination.
        /// Recommend using FromRoutingService, IdleAtDepot, or ViaPreviousbus instead.
        /// </summary>
        /// <param name="toStart">The route that is taken before visiting the first stop.
        /// This may simply be the bus idling at its depot.</param>
        /// <param name="stops">The stops this bus will visit. Must be finite.</param>
        public Bus(Route toStart, IEnumerable<Stop> stops/*, TimeSpan firstAvailableTime*/)
        {
            _start = toStart.Source;
            //_startingTime = firstAvailableTime;
            //Strictly evaluate the stops to generate a stop array
            //No way to check in advance that this is finite, that I know of.
            var stopsArray = stops.ToArray();

            //A bus that does not visit any stops is a trivial bus.
            //The initial route is ignored entirely.
            //Set the trivial flag and return.
            if (stopsArray.Length == 0)
            {
                _route = new BusRouteLeg[1];
                _route[0] = new BusRouteLeg(toStart, 0);
                _passengers = 0;
                _duration = TimeSpan.Zero;
                _timeStudentSpentOnBus = TimeSpan.Zero;
                _distance = 0;
                _end = _start;
                _trivialFlag = true;
                return;
            }

            //Otherwise, this is not a trivial bus:
            _trivialFlag = false;

            //The first leg of the route is the given initial route with no passengers.
            var startingLeg = new BusRouteLeg(toStart,0);

            //Accumulates the amount of time the bus has no riders on it
            var noRiderTimeAccum = TimeSpan.Zero;

            //Accumulates the amount of time the bus has a rider on it.
            var riderTimeAccum = TimeSpan.Zero;

            //Accumulates the distance travelled by the bus.
            double distanceAccum = 0;

            //Accumulates the passengers on this bus.
            int passengerAccum = 0;

            //Initialize the array of bus routes. The first entry is a leg that
            // connects the starting route to the first stop.
            _route = new BusRouteLeg[stopsArray.Length];
            _route[0] = startingLeg;

            //Intermediate routes are generated by connecting previous routes to their
            // following stops
            for (int i = 1; i < _route.Length; i++)
                _route[i] = new BusRouteLeg(_route[i - 1], stopsArray[i]);

            //Now we accumulate distance, rider time, and total time. This is done as follows:
            //We initialize the index by which we will be retrieving bus legs to 0
            int routeIndex = 0;

            //So long as there are no passengers at the stops, add to noRiderTimeAccum.
            //Add distances to distanceAccum as well.
            for (;routeIndex < _route.Length && _route[routeIndex].Passengers == 0; routeIndex++)
            {
                noRiderTimeAccum += _route[routeIndex].Route.Duration;
                distanceAccum += _route[routeIndex].Route.Distance;
            }
            //Keep going after picking up the first passengers, but add to riderTimeAccum instead.
            for (; routeIndex < _route.Length; routeIndex++)
            {
                riderTimeAccum += _route[routeIndex].Route.Duration;
                distanceAccum += _route[routeIndex].Route.Distance;
                passengerAccum += _route[routeIndex].Passengers;
            }

            //Assign the resulting sums to their relevant fields
            _timeStudentSpentOnBus = riderTimeAccum;
            _duration = riderTimeAccum + noRiderTimeAccum;
            _distance = distanceAccum;
            _passengers = passengerAccum;
            _start = toStart.Source;
            _end = _route[routeIndex - 1].Route.Destination;
        }

        /// <summary>
        /// Constructs a new bus using the provided routing service.
        /// </summary>
        /// <param name="routeService">The routing service that will build the bus.</param>
        /// <param name="depot">The depot where this bus will begin its service.</param>
        /// <param name="school">The school this bus is servicing.</param>
        /// <param name="stops">The stops that this bus is servicing.</param>
        /// <returns>A bus that starts at the provided depot and services all its routes.</returns>
        public static Bus FromRoutingService
           (IRoutingService routeService,
            Location depot,
            Location school,
            IEnumerable<Stop> stops)
        {
          return new Bus(Route.IdleInPlace(depot, TimeSpan.Zero, routeService), stops);
        }

        /// <summary>
        /// Deserialize a bus and have it point to the current routing service.
        /// </summary>
        /// <param name="routeService">A routing service to be pointed to by the routes.</param>
        /// <param name="file">A file to deserialize.</param>
        /// <returns></returns>
        //public static Bus FromFile(IRoutingService routeService, System.IO.FileStream file)
        //{
        //    var bf = new BinaryFormatter();
        //    Bus output = (Bus)bf.Deserialize(file);
        //    foreach(BusRouteLeg route in output._route) route.Route.RoutingService = routeService;
        //    return output;
        //}
        /// <summary>
        /// Uses the given bus to generate a new bus
        /// </summary>
        /// <param name="previousBus">The bus that travels to where this bus starts.</param>
        /// <param name="school">The school that this bus will service.</param>
        /// <param name="stops">The stops that this bus services.</param>
        /// <returns>A bus that starts where the previous bus ended and carries out its route</returns>
        public static Bus ViaPreviousBus(Bus previousBus, Location school,
            IEnumerable<Stop> stops)
        {
            return FromRoutingService(previousBus.Directions.First().RoutingService,
                previousBus.EndingLocation, school, stops);
        }

        /// <summary>
        /// Creates a bunch of empty buses sitting at a depot, doing nothing.
        /// </summary>
        /// <param name="routingService"></param>
        /// <param name="depot"></param>
        /// <param name="numberOfBuses"></param>
        /// <returns></returns>
        public static IEnumerable<Bus> PutBusesAtDepot(IRoutingService routingService,
            Location depot, uint numberOfBuses)
        {
            for (int i = 0; i <= numberOfBuses; i++)
                yield return FromRoutingService(routingService, depot, depot, new Stop[0]);
        }

        //protected Bus(Bus toDelay, TimeSpan delayAmount)
        //{
        //    _distance = toDelay._distance;
        //    _duration = toDelay._duration;
        //    _end = toDelay._end;
        //    _endingTime = toDelay._endingTime + delayAmount;
        //    _passengers = toDelay._passengers;
        //    _route = toDelay._route;
        //    _start = toDelay._start;
        //    _startingTime = toDelay._startingTime + delayAmount;
        //    _timeStudentSpentOnBus = toDelay._timeStudentSpentOnBus;
        //    _trivialFlag = toDelay._trivialFlag;
        //}

        //public Bus DelayBus(Bus toDelay, TimeSpan delayAmount)
        //{
        //    return new Bus(toDelay, delayAmount);
        //}

        #endregion

        #region Functions

        /// <summary>
        /// Converts a bus to a route.
        /// </summary>
        /// <param name="busToConvert">The bus to turn into a route.</param>
        /// <returns>A route that represents the resulting bus.</returns>
        public static Route ConvertToRoute(Bus busToConvert)
        {
            return busToConvert.Directions.Aggregate((x, y) => x + y);
        }

        #endregion

        #region IDeserializationCallback Members

        public void OnDeserialization(object sender)
        {
            this.RoutingService = 
                new ErrorRoutingService("Deserialized Bus object must be provided valid routing service after deserialization");
        }

        #endregion

    }
}
